﻿#include <array>
#include <bitset>
#include <QModbusTcpServer>
#include <QDebug>
#include <QTimer>
#include <QJsonObject>
#include <QJsonArray>
#include <QTcpSocket>
#include <QHostAddress>
#include <QThread>
#include "Frameworks/DrillingrigMediator.h"
#include "Frameworks/MediatorEventDefine.h"
#include "ModbusClientProxy.h"
#include "ControlBridge.h"


constexpr const int RECONNECT_COUNT_MAX = 3;
constexpr const int TIMEOUT_MAX = 3000;

class TcpObserver : public QModbusTcpConnectionObserver
{
	ControlBridge* m_bridge = nullptr;
public:
	TcpObserver(ControlBridge* bridge)
		: m_bridge(bridge) {

	}
	bool acceptNewConnection(QTcpSocket* newClient) {
		if (m_bridge->m_clientList.isEmpty()) {
			m_bridge->m_clientList.append(newClient);
		}
		else {
			return false;
		}
		if (!m_bridge->m_timer->isActive())
			m_bridge->m_timer->start(50);
		m_bridge->m_mediator->reportLog(QStringLiteral("连接操作箱:") +
									  newClient->peerAddress().toString());
		QObject::connect(newClient, &QAbstractSocket::stateChanged, m_bridge,
						&ControlBridge::onTcpStateChanged);
		return true;
	}
};

QStringView ControlBridge::identity() const {
	return ControlBridge::Identity;
}

ControlBridge::ControlBridge(QObject* parent)
	: QObject(parent) {
	QMetaObject::invokeMethod(this, [this] {
		m_server = new QModbusTcpServer(this);
		m_timer = new QTimer(this);
		QModbusDataUnitMap reg;
		reg.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, 512 });
		m_server->setMap(reg);
		for (int i = 0; i < 512; i++) {
			m_server->setData(QModbusDataUnit::HoldingRegisters, i, 0);
		}

		m_server->installConnectionObserver(new TcpObserver(this));
		connect(m_timer, &QTimer::timeout, this, &ControlBridge::onTimeout);
		connect(m_server, &QModbusTcpServer::modbusClientDisconnected, this, [this](QTcpSocket* modbusClient) {
			m_clientList.removeOne(modbusClient);
			m_mediator->reportLog(QStringLiteral("断开操作箱:") + modbusClient->peerAddress().toString());
			if (m_clientList.isEmpty()) {
				QJsonObject root;
				QJsonArray jsValues;
				constexpr std::array<int, 50> resetValues = { 0 };
				std::transform(std::cbegin(resetValues), std::cend(resetValues), std::back_inserter(jsValues), [](auto v) { return v; });
				root["startAddress"] = 0;
				root["values"] = jsValues;
				root[ME_MSG_KEY] = ME_TRANSMIT.toString();
				send(root);
				m_timer->stop();
			}
		});
		connect(m_server, &QModbusServer::dataWritten, this, &ControlBridge::updateData);
		connect(m_server, &QModbusServer::stateChanged, this, &ControlBridge::onStateChanged);
		connect(m_server, &QModbusServer::errorOccurred, this, &ControlBridge::handleDeviceError);
		//m_server->setValue(QModbusServer::DeviceBusy, 0x0000);
		m_processMsgMap[ME_UPDATEREGISTER] = std::bind(&ControlBridge::processUpdateRegister, this, std::placeholders::_1);
		m_processMsgMap[ME_LOCKEVENT] = std::bind(&ControlBridge::processLock, this, std::placeholders::_1);
		m_processMsgMap[ME_UNLOCKEVENT] = std::bind(&ControlBridge::processUnlock, this, std::placeholders::_1);
		m_processMsgMap[ME_CONNECTSTATE] = std::bind(&ControlBridge::processConnectState, this, std::placeholders::_1);
	}, Qt::QueuedConnection);
}

ControlBridge::~ControlBridge() {
	m_mediator->unRegisterAction(ControlBridge::Identity);
	m_mediator = nullptr;
}

bool ControlBridge::connectDevice(QString const& host, quint16 port, int device) {
	qDebug() << "start listen to host:" << host << "\tport:" << port << "\tdevice: " << device;
	m_hostStr = host;
	m_port = port;
	m_startAddress = device;
	bool result = false;
	QMetaObject::invokeMethod(this, [this]()->bool {
		m_server->setConnectionParameter(QModbusDevice::NetworkPortParameter, m_port);
		m_server->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_hostStr);
		m_server->setServerAddress(m_startAddress);
		return true;
		}, Qt::BlockingQueuedConnection, &result);

	return result;
}

void ControlBridge::update(QJsonObject const& msg) {
	QMetaObject::invokeMethod(this, [msg, this] {
		auto&& commandStr = msg[ME_MSG_KEY].toString();
		auto iter = m_processMsgMap.find(commandStr);
		if (iter != std::cend(m_processMsgMap)) {
			iter->second(std::cref(msg));
		}
	});
}

void ControlBridge::send(QJsonObject const& msg) {
	if (m_mediator) {
		m_mediator->push(msg, ControlBridge::Identity);
	}
}

void ControlBridge::send(QJsonObject const& msg, QStringView to) {
	if (m_mediator) {
		m_mediator->push(msg, ControlBridge::Identity, to);
	}
}

void ControlBridge::setMediator(AbstruactEventMediator* mediator) {
	m_mediator = mediator;
}

bool ControlBridge::lock() {
	m_timer->stop();
	m_server->disconnectDevice();
	return m_server->error() == QModbusDevice::NoError;

}

bool ControlBridge::unlock() {
	m_server->setConnectionParameter(QModbusDevice::NetworkPortParameter, m_port);
	m_server->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_hostStr);
	m_server->setServerAddress(m_startAddress);
	m_server->connectDevice();
	return m_server->error() == QModbusDevice::NoError;

}

void ControlBridge::onTcpStateChanged(QAbstractSocket::SocketState socketState) {
	switch (socketState) {
	case QAbstractSocket::ClosingState:
	case QAbstractSocket::UnconnectedState:
		if (m_clientList.size() <= 1) {
			for (int i = 0; i < 512; i++) {
				m_server->setData(QModbusDataUnit::HoldingRegisters, i, 0);
			}
			QJsonObject root;
			QJsonArray jsValues;
			for (int i = 0; i < 50; ++i) {
				quint16 value = 0;
				m_server->data(QModbusDataUnit::HoldingRegisters, quint16(i), &value);
				jsValues.append(value);
			}
			root["startAddress"] = 0;
			root["values"] = jsValues;
			root[ME_MSG_KEY] = ME_TRANSMIT.toString();
			send(root);
			m_timer->stop();
		}
		break;
	case QAbstractSocket::ConnectedState:
		if (!m_timer->isActive())
			m_timer->start(50);
		break;
	default:
		break;
	}
}

void ControlBridge::processUpdateRegister(QJsonObject const& obj) {
	auto&& valueObj = obj["values"].toObject();
	for (int i = 50; i < 312; i++) {
		auto&& key = QString::number(i);
		if (valueObj.contains(key)) {
			auto value = quint16(valueObj[key].toInt());
			m_server->setData(QModbusDataUnit::HoldingRegisters, i, value);
		}
	}
}

void ControlBridge::processLock(QJsonObject const& obj) {
	reportLog(QStringLiteral("锁屏时间") + obj["msg"].toString());
	lock();
}

void ControlBridge::processUnlock(QJsonObject const& obj) {
	reportLog(QStringLiteral("解锁时间") + obj["msg"].toString());
	unlock();
}

void ControlBridge::processConnectState(QJsonObject const& obj) {
	auto&& state = obj["state"].toInt();
	if (state == QModbusDevice::ConnectedState) {
		if (m_server->state() != QModbusDevice::ConnectedState
			&& m_server->state() != QModbusDevice::ConnectingState) {
			m_server->setConnectionParameter(QModbusDevice::NetworkPortParameter, m_port);
			m_server->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_hostStr);
			m_server->setServerAddress(m_startAddress);
			m_server->connectDevice();
		}
	}
	else if (state == QModbusDevice::UnconnectedState || state == QModbusDevice::ClosingState) {
		m_server->disconnectDevice();
	}
}

void ControlBridge::reportLog(QString const& log, int lev) {
	QJsonObject obj;
	obj[ME_MSG_KEY] = ME_REPORTLOG.toString();
	obj["msg"] = log;
	obj["level"] = lev;
	send(obj);
}

void ControlBridge::updateData(QModbusDataUnit::RegisterType table, int address, int size) {
	if (table != QModbusDataUnit::HoldingRegisters || address != 0) return;
	m_interval = 0;
}

void ControlBridge::onStateChanged(QModbusDevice::State state) {
	if (state == QModbusDevice::UnconnectedState) {
		qDebug() << QStringView(u"Server is unconnected!");
		m_mediator->reportLog(u"转发服务连接断开");
	}
	else if (state == QModbusDevice::ConnectingState) {
		qDebug() << QStringView(u"Server is connecting!");
	}
	else if (state == QModbusDevice::ConnectedState) {
		qDebug() << QStringView(u"Server is connected!");
		m_mediator->reportLog(u"转发服务连接成功");
	}
	else if (state == QModbusDevice::ClosingState) {
		qDebug() << QStringView(u"Server is closing!");
		m_mediator->reportLog(u"转发服务连接关闭");
	}
}

void ControlBridge::handleDeviceError(QModbusDevice::Error error) {
	if (error == QModbusDevice::NoError || !m_server)
		return;
	if (error == QModbusDevice::ConnectionError || error == QModbusDevice::TimeoutError) {
		m_reConnectCount++;
		if (m_reConnectCount > RECONNECT_COUNT_MAX) {
			qWarning() << QStringView(u"the try reconnect number of attempts reached the upper limit");
			qWarning() << QStringView(u"调整网络环境后,重启软件尝试.");
			m_mediator->reportLog(u"转发服务网络断开故障，10秒后重连", 1);
			QTimer::singleShot(10000, [this] {
				m_reConnectCount = 0;
				m_server->connectDevice();
			});
			return;
		}
		if (!m_server->connectDevice()) {
			qWarning() << QStringView(u"try reconnect has error: ") << m_server->errorString();
			m_reConnectCount = 0;
			return;
		}
	}
	qDebug() << m_server->errorString();
}

void ControlBridge::onTimeout() {
	QJsonObject root;
	QJsonArray jsValues;
#ifndef _DEBUG
	if (m_interval > TIMEOUT_MAX) {
		for (int i = 0; i < 50; i++) { jsValues.append(0); }
		root["startAddress"] = 0;
		root["values"] = jsValues;
		root[ME_MSG_KEY] = ME_TRANSMIT.toString();
		send(root);
		if (!m_clientList.isEmpty()) {
			m_clientList[0]->disconnectFromHost();
		}
		return;
	}
#endif
	quint16 value = 0;
	for (int i = 0; i < 50; ++i) {
		m_server->data(QModbusDataUnit::HoldingRegisters, quint16(i), &value);
		jsValues.append(value);
	}
	root["startAddress"] = 0;
	root["values"] = jsValues;
	root[ME_MSG_KEY] = ME_TRANSMIT.toString();
	send(root);
	m_interval += m_timer->interval();
}
